use std::ops::Range;

use chrono::{DateTime, FixedOffset, Utc};
use sqlx::{
    FromRow, Row,
    postgres::{PgRow, types::PgRange},
    types::Uuid,
};

use crate::{
    error::Error,
    reservation::{Reservation, Status},
    types::{NativeBound, Validator, status::RsvpStatus},
    utils::{convert_to_timestamp, convert_to_utc_time},
};

use super::validate_range;

impl Reservation {
    pub fn new_pending(
        user_id: impl Into<String>,
        resource_id: impl Into<String>,
        start: DateTime<FixedOffset>,
        end: DateTime<FixedOffset>,
    ) -> Self {
        Self {
            id: "".to_string(),
            user_id: user_id.into(),
            resource_id: resource_id.into(),
            status: Status::Pending as i32,
            start: Some(convert_to_timestamp(start.with_timezone(&Utc))),
            end: Some(convert_to_timestamp(end.with_timezone(&Utc))),
            note: "test note".to_string(),
        }
    }

    pub fn get_timespan(&self) -> Range<DateTime<Utc>> {
        let start = convert_to_utc_time(*self.start.as_ref().unwrap());
        let end = convert_to_utc_time(*self.end.as_ref().unwrap());
        Range { start, end }
    }
}

impl Validator for Reservation {
    fn validate(&self) -> Result<(), Error> {
        if self.resource_id.is_empty() {
            return Err(Error::InvalidId("resource_id", self.resource_id.clone()));
        } else if self.user_id.is_empty() {
            return Err(Error::InvalidId("user_id", self.user_id.clone()));
        }

        validate_range(self.start.as_ref(), self.end.as_ref())?;

        Ok(())
    }
}

impl FromRow<'_, PgRow> for Reservation {
    fn from_row(row: &PgRow) -> Result<Self, sqlx::Error> {
        let uuid: Uuid = row.get("id");
        let range: PgRange<DateTime<Utc>> = row.get("timespan");
        let range: NativeBound<DateTime<Utc>> = range.into();

        assert!(range.start.is_some() && range.end.is_some());
        let start = range.start.unwrap();
        let end = range.end.unwrap();

        let status: RsvpStatus = row.get("status");

        Ok(Self {
            id: uuid.to_string(),
            user_id: row.get("user_id"),
            resource_id: row.get("resource_id"),
            status: Status::from(status) as i32,
            start: Some(convert_to_timestamp(start)),
            end: Some(convert_to_timestamp(end)),
            note: row.get("note"),
        })
    }
}
